package com.atguigu.gulimall.product.service.impl;


import com.alibaba.fastjson.JSON;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    //    @Autowired
//    CategoryDao categoryDao;
    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redisson;

    @Autowired
    ApplicationContext applicationContext;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //1  查询出所有的 菜单项
        List<CategoryEntity> entities = baseMapper.selectList(null);

        List<CategoryEntity> treeEntities = entities.stream()
                .filter(
                        // 1.1 先找出 第一级 类
                        categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(categoryEntity -> {
                    //2  把子菜单封装进父菜单
                    categoryEntity.setChildren(getChildrens(categoryEntity, entities));
                    return categoryEntity;
                })
                .sorted((menu1, menu2) -> {
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                })
                .collect(Collectors.toList());
        return treeEntities;
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO  1、检查当前删除的菜单，是否被别的地方引用

        //逻辑删除
        baseMapper.deleteBatchIds(asList);
    }

    //[2,25,225]
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        return null;
    }

    /**
     * 级联更新所有关联的数据
     *    采用  失效模式
     *    如果是 双写模式 那么就 使用 @CachePut  但必须方法是有返回值的前提
     * @param category
     */
    @Caching(evict = {
            // 这些是缓存删除
            @CacheEvict(value = "category",key="'getLevelOne'"),
            @CacheEvict(value = "category",key="'getCateLogJsonByReadWriteLock'")
    })
    @Transactional
    @Override
    public void updateCascade(CategoryEntity category) {


        // 1 先更新 本表所关联的数据
        this.updateById(category);
        String categoryName = category.getName();
        if (StringUtils.isNotEmpty(categoryName)) {
            categoryBrandRelationService.updateCategory(category.getCatId(), categoryName);
        }
    }

    /**
     * 获取当前 分类的完成路径
     *
     * @param catelogId
     * @return
     */
    @Override
    public Long[] getParentsById(Long catelogId) {
        List<Long> parentsId = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, parentsId);
        // 反序集合 因为最后才添加的 第一级分类的id
        Collections.reverse(parentPath);
        return parentPath.toArray(new Long[parentPath.size()]);
    }

    /**
     * 首页展现 一级分类
     *
     * @return
     */
    @Cacheable(value = "category",key="#root.methodName")
    @Override
    public List<CategoryEntity> getLevelOne() {
        List<CategoryEntity> list = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return list;
    }

    /**
     *   @Cacheable(value = "category",key="#root.methodName")  如果有缓存就不再进入方法 而直接返回
     * @return
     */

    @Override
    public Map<String, List<Catelog2Vo>> getCateLogJson(){
        CategoryServiceImpl categoryService = applicationContext.getBean(CategoryServiceImpl.class);
        Map<String, List<Catelog2Vo>>    categoryJson=categoryService.getCateLogJsonByReadWriteLock();
        return  categoryJson;
    }

    /**
     * sync = true  防止缓存击穿
     * @return
     */
    @Cacheable(value = "category",key="#root.methodName",sync = true)
    public Map<String, List<Catelog2Vo>> getCateLogJsonByReadWriteLock(){
        // 查询出所有 分类 数据
        List<CategoryEntity> categoryEntityList = this.list();
        //查找出一级分类
        List<CategoryEntity> levelOne = getParent_cid(categoryEntityList, 0l);
        Map<String, List<Catelog2Vo>> cateLogJson = levelOne.stream().collect(Collectors.toMap(k -> {
            return k.getCatId().toString();
        }, v -> {
            // 查找当前 一级分类关联的二级分类
            List<CategoryEntity> levelTwo = getParent_cid(categoryEntityList, v.getCatId());
            if (levelTwo == null || levelTwo.size() == 0) {
                return null;
            }

            List<Catelog2Vo> catelog2VoList = levelTwo.stream().map(l2 -> {
                Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());

                // 查找二级分类关联的 三级分类
                List<CategoryEntity> levelThree = getParent_cid(categoryEntityList, l2.getCatId());
                // 三级分类
                List<Catelog2Vo.Category3Vo> category3Vos = levelThree.stream().map((l3) -> {
                    Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                    return category3Vo;
                }).collect(Collectors.toList());

                catelog2Vo.setCatalog3List(category3Vos);
                return catelog2Vo;
            }).collect(Collectors.toList());
            return catelog2VoList;
        }));
        return cateLogJson;
    }




    /**
     * 缓存建设  categoryJson
     *
     * @return
     */

    public Map<String, List<Catelog2Vo>> getCateLogJson1() {
        String categoyJson = stringRedisTemplate.opsForValue().get("categoryJson");
        if (!StringUtils.isNotEmpty(categoyJson)) {
            Map<String, List<Catelog2Vo>> cateLogJsonFromDb = getCateLogJsonFromDbByRedisLock();

            return cateLogJsonFromDb;
        }
        // 把 获取的json字符串转为  相应的Bean
        Map<String, List<Catelog2Vo>> parseFromRedis = (Map<String, List<Catelog2Vo>>) JSON.parse(categoyJson);
        return parseFromRedis;
    }

    /**
     * redisson 处理
     * @return
     * @throws InterruptedException
     */
    public Map<String, List<Catelog2Vo>> getCateLogJsonFromDbByRedisson() throws InterruptedException {
//        这是使用 redisson的读写锁 读写锁可以保证  读-读下 没有阻塞  在 更新数据库 为保证数据的一致性  在更新时加上 写锁
//        RReadWriteLock readWriteLock = redisson.getReadWriteLock("cateLogJson-Lock");
//        RLock rLock = readWriteLock.readLock();

        // 基于 redisson的 分布式锁
        RLock lock = redisson.getLock("cateLogJson-Lock");
        Map<String, List<Catelog2Vo>> makeCacheBulid = null;
        if (lock.tryLock(10,TimeUnit.SECONDS)) {
            try {
                makeCacheBulid = makeCacheBulid();
            } finally {
               lock.unlock();
            }
        }
        return makeCacheBulid;
    }



    /**
     * 解决缓存击穿  用分布式锁 执行
     *
     * @return
     */

    public Map<String, List<Catelog2Vo>> getCateLogJsonFromDbByRedisLock() {
        // 生产uuid  结合 lua脚本防止 误删
        String uuId = UUID.randomUUID().toString();
        // 开始上锁
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuId, 10, TimeUnit.SECONDS);

        Map<String, List<Catelog2Vo>> makeCacheBulid = null;
        if (lock) {
            try {
                makeCacheBulid = makeCacheBulid();
            } finally {
                // 释放锁
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then  return redis.call('del', KEYS[1]) else return 0 end ";
                String lockValue = stringRedisTemplate.opsForValue().get("lock");
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(lockValue), uuId);
            }


        } else {
            // 重试获取锁
            System.out.println("获取锁失败。。。。。重试");
            try {
                Thread.sleep(300);
                getCateLogJsonFromDbByRedisLock();
            } catch (InterruptedException e) {
            }
        }

        return makeCacheBulid;
    }


    private Map<String, List<Catelog2Vo>> makeCacheBulid() {
        // 在获取到锁时先判断 缓存是否重建 以免再一次 查询数据库  因为有可能  前一个获取 分布式锁的线程对缓存已经重建
        String categoyJson = stringRedisTemplate.opsForValue().get("categoryJson");
        if (StringUtils.isNotEmpty(categoyJson)) {
            return (Map<String, List<Catelog2Vo>>) JSON.parse(categoyJson);
        }

        System.out.println("查询数据库。。。。。。");
        // 查询出所有 分类 数据
        List<CategoryEntity> categoryEntityList = this.list();
        //查找出一级分类
        List<CategoryEntity> levelOne = getParent_cid(categoryEntityList, 0l);
        Map<String, List<Catelog2Vo>> cateLogJson = levelOne.stream().collect(Collectors.toMap(k -> {
            return k.getCatId().toString();
        }, v -> {
            // 查找当前 一级分类关联的二级分类
            List<CategoryEntity> levelTwo = getParent_cid(categoryEntityList, v.getCatId());
            if (levelTwo == null || levelTwo.size() == 0) {
                return null;
            }

            List<Catelog2Vo> catelog2VoList = levelTwo.stream().map(l2 -> {
                Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());

                // 查找二级分类关联的 三级分类
                List<CategoryEntity> levelThree = getParent_cid(categoryEntityList, l2.getCatId());
                // 三级分类
                List<Catelog2Vo.Category3Vo> category3Vos = levelThree.stream().map((l3) -> {
                    Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                    return category3Vo;
                }).collect(Collectors.toList());

                catelog2Vo.setCatalog3List(category3Vos);
                return catelog2Vo;
            }).collect(Collectors.toList());

            return catelog2VoList;
        }));
       //缓存重建
        stringRedisTemplate.opsForValue().set("categoryJson", JSON.toJSONString(cateLogJson));
        return cateLogJson;
    }


    // 与 查询 所有 分类数据有关
    private List<CategoryEntity> getParent_cid(List<CategoryEntity> categoryEntityList, Long parent_cid) {
        List<CategoryEntity> collect = categoryEntityList
                .stream()
                .filter(item -> item.getParentCid() == parent_cid)
                .collect(Collectors.toList());
        return collect;
    }

    //225,25,2
    private List<Long> findParentPath(Long catelogId, List<Long> paths) {
        paths.add(catelogId);
        Long parentCid = this.getById(catelogId).getParentCid();
        if (parentCid != 0) {
            findParentPath(parentCid, paths);
        }
        return paths;
    }


    //递归查找所有菜单的子菜单
    private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {

        List<CategoryEntity> collect = all.stream()
                .filter(categoryEntity ->
                        // 过滤出 当前 级类 的子分类
                        categoryEntity.getParentCid() == root.getCatId())
                .map(categoryEntity -> {
                            // 如果当前子分类 还有 子分类 继续 递归 查找 下一级的子分类
                            categoryEntity.setChildren(getChildrens(categoryEntity, all));
                            return categoryEntity;
                        }
                )
                .sorted((menu1, menu2) -> {
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                })
                .collect(Collectors.toList());
        return collect;
    }


}